from fastapi import Depends, Request
from fastapi.security import OAuth2PasswordBearer
from fastapi.exceptions import HTTPException
from sqlalchemy.orm import Session
from redis import StrictRedis
from pyfuseki import AsyncFuseki
import motor.motor_asyncio
from typing import Optional

from elasticsearch import AsyncElasticsearch
from exceptions import TokenVerifyException
from config import redis_conf, es_conf, fuseki_conf, mongo_conf
import mysql_connect


async def get_mysql_db() -> Session:
    """
    获取数据库对象的函数
    """
    db = mysql_connect.SessionLocal()
    try:
        yield db
    finally:
        db.close()


async def get_redis() -> StrictRedis:
    """
    获取Redis对象
    """
    redis = StrictRedis(host=redis_conf.HOST,
                        port=redis_conf.PORT,
                        db=redis_conf.db,
                        password=redis_conf.PASSWORD)
    try:
        yield redis
    finally:
        redis.close()


async def get_es() -> AsyncElasticsearch:
    """
    获取 ES Client 对象
    """
    es = AsyncElasticsearch(hosts=es_conf.HOSTS,
                            timeout=es_conf.TIMEOUT,
                            **es_conf.KWARGS)
    return es


async def get_fuseki() -> AsyncFuseki:
    """
    获取 Async Fuseki 对象
    """
    return AsyncFuseki(fuseki_url=f'{fuseki_conf.HOST}:{fuseki_conf.PORT}',
                       db_name=fuseki_conf.DB_NAME)


async def get_mongo_db() -> motor.motor_asyncio.AsyncIOMotorDatabase:
    """
    获取 MongoDB 对象
    """
    client = motor.motor_asyncio.AsyncIOMotorClient(host=mongo_conf.HOST, port=mongo_conf.PORT)
    db = client[mongo_conf.DB_NAME]
    try:
        yield db
    finally:
        client.close()



__oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/login/token")

async def fetch_token(request: Request) -> Optional[str]:
    """
    在 request 中获取到 oauth2 认证所需要的信息
    :return: 取出的 token
    """
    try:
        token = await __oauth2_scheme(request)
    except HTTPException as e:
        raise TokenVerifyException(e.detail)
    return token



def verify_token(token: str = Depends(fetch_token),
                 redis: StrictRedis = Depends(get_redis)) -> str:
    """
    根据请求头部的 Authorization 字段，在 Redis 进行验证并获取用户的 username
    :return: 验证成功时返回用户的 username，验证失败则抛出异常
    :raise: TokenVerifyException 验证失败时抛出此异常
    """
    # 验证 token 是否为空
    if token is None:
        raise TokenVerifyException()
    # 查询 redis_db 进行验证
    username = redis.get(token)
    if username is None:
        raise TokenVerifyException()
    return username
